/* makeLexerClass.js v1.3.0-gamma */

const repeat = (rep, num) => new Array(num + 1).join(rep)
const { concat } = []
const flatten = arr => concat.apply([], arr)

/**
 * 表示一个处理文本行的类。
 *
 * 此类用于处理文本数据，允许将文本分割成单独的行进行处理。它支持处理不同类型的换行符，
 * 并提供了遍历每一行的功能。
 */

class Lines {
  /**
   * 构造 Lines 类的实例。
   *
   * @param {string} str 要处理的文本数据。
   * @param {string} [nlSpliter='both'] 用于分割文本行的换行符或字符。默认为 'both'，
   * 即将 '\r\n' 和 '\n' 都视为换行符。
   */
  constructor(str, nlSpliter = 'both') {
    // 初始化 data 数组，根据指定的换行符将输入字符串分割成行。
    this.data = nlSpliter === 'both' ? str.replace(/\r\n/g, '\n').split('\n') :
                                       str.split(nlSpliter)
    // 初始化行号为 -1，为第一次调用 nextLine 获取第一行做准备。
    this.ln = -1
    // 调用 nextLine 获取第一行文本。
    this.nextLine()
  }

  /**
   * 判断是否到达文件末尾（EOF）。
   */
  get eof() { return this.ln === this.data.length - 1 && this.line.eol }

  /**
   * 获取下一行。
   */
  nextLine() {
    this.ln++
    this.line = new Line(this.data[this.ln])
  }
}


/**
 * 表示文本行的类
 */
class Line {
  /**
   * 构造函数，用于创建Line对象
   * @param {string} str - 行的内容
   * @param {string} [rest=str] - 剩余的文本内容，默认与str相同
   * @param {number} [col=0] - 当前行的起始列号，默认为0
   */
  constructor(str, rest, col = 0) {
    this.str = str; // 当前行的内容
    this.rest = rest || str; // 剩余的文本内容
    this.col = col; // 当前行的起始列号
  }

  /**
   * 返回一个布尔值，表示当前行是否结束
   */
  get eol() { return this.rest.length === 0; }

  /**
   * 将当前行推进指定的字符数
   * @param {number} num - 要推进的字符数
   */
  advance(num) {
    this.col += num; // 更新当前列号
    this.rest = this.rest.substr(num); // 更新剩余的文本内容
  }

  /**
   * 将当前行的文本截断为指定的长度，并保存被截断的部分
   * @param {number} length - 截断后的文本长度
   */
  cutoff(length) {
    this.cutoffRest = this.rest.substr(length); // 保存被截断的文本部分
    this.rest = this.rest.substr(0, length); // 截断文本
    this.isCutoff = true; // 标记当前行已被截断
  }

  /**
   * 将之前截断的部分重新加入到当前行的文本中
   */
  joinCutoff() {
    this.rest += this.cutoffRest; // 重新加入之前截断的文本部分
    this.isCutoff = false; // 标记当前行不再被截断
  }
}


/**
 * 用于lexer.isSeq()中的顺序标记匹配数据
 * @param {Lexer} lexer - 构造函数接收的词法分析器实例
 */
class SeqData {
  constructor(lexer) {
    this.lexer = lexer
    this.lines = lexer.lines
    this.setCurrLine(lexer.lines.ln)
    this.ref = 1
  }

  get eol() { return this.line.eol }
  get eof() {
    // 检查是否到达文件末尾
    return this.ln === this.lines.data.length - 1 && this.line.eol
  }
  get hasNextLine() { return this.ln < this.lines.data.length - 1 }

  /**
   * 设置当前行
   * @param {number} ln - 行号
   */
  setCurrLine(ln) {
    this.ln = ln
    this.line = new Line(this.lines.data[ln])
  }

  nextLine() { this.setCurrLine(this.ln + 1) }

  /**
   * 保存状态以便后续恢复
   */
  keepState() {
    this.kstate = { ln: this.ln, line: { ...this.line } }
  }

  /**
   * 恢复之前保存的状态
   */
  restoreState() {
    this.ln = this.kstate.ln
    const { str, rest, col } = this.kstate.line
    this.line = new Line(str, rest, col)
  }

  /**
   * 尝试匹配并消耗指定的标记
   * @param {string} token - 待匹配的标记
   * @returns {boolean} 是否成功匹配
   */
  eatToTest(token) {
    if (token === 'NL') {
      if (this.eof || !this.line.eol) return false
      this.nextLine()
      return true
    }

    if (token === 'NL!') {
      if (this.hasNextLine) {
        this.nextLine()
        return true
      }
      return false
    }

    const pattern = this.lexer.getPattern(token)
    if (!(pattern instanceof RegExp)) return this.lexer.is(token)

    const matched = this.line.rest.match(pattern)
    if (!matched) return false
    this.line.advance(matched[0].length)
    return true
  }
}

/**
 * 默认的正则表达式模式
 */
const defaultPatterns = {
  S: ' ',
  SS: ' +',
  ALL: '.+'
}

/**
 * 安排关键字，使其可用于正则表达式匹配
 * @param {Object} patterns - 包含关键字的对象
 * @returns {Object} 修改后的对象
 */
const arrangeKeywords = patterns => {
  const { keywords } = patterns
  if (!keywords) return patterns
  patterns.keywords = `(${keywords})\\b`
  keywords.split('|').forEach(keyword => {
    patterns[keyword] = keyword + '\\b'
  })
  return patterns
}

/**
 * 处理复合标记数组
 * @param {Array} arr - 标记数组
 * @returns {Object} 处理后的复合标记对象
 */
const makeCompoundTokens = arr => {
  const tokensStr = arr[0]
  if (/^!/.test(tokensStr)) {
    if (/[ \|]/.test(tokensStr)) throw new Error('Not supported.')
    return { not: tokensStr.substr(1) }
  }
  if (/ /.test(tokensStr)) return { seq: tokensStr.split(' ') }
  if (/\|/.test(tokensStr)) return { some: tokensStr.split('|') }
  throw new Error('Incorrect compound tokens: ' + tokensStr)
}

/**
 * 创建正则表达式模式
 * @param {string} str - 正则表达式字符串
 * @returns {RegExp|Object} 返回正则表达式或处理后的复合标记对象
 */
const makePattern = str => {
  if (Array.isArray(str)) return makeCompoundTokens(str)
  if (/\|/.test(str)) str = `(${str})`
  return new RegExp('^' + str)
}

/**
 * 创建嵌入式词法分析器的数据
 * @param {string} rawIdent - 原始标识符
 * @param {Function} EmbedLexer - 嵌入式词法分析器构造函数
 * @returns {Object} 嵌入式词法分析器数据
 */
const makeEmbedData = (rawIdent, EmbedLexer) => {
  const ident = rawIdent.substring(2, rawIdent.length - 2)
  const result = {}
  result[ident] = new EmbedLexer()
  return result
}

/**
 * 获取并处理模式
 * @param {Object} patterns - 模式对象
 * @returns {Array} 处理后的模式数组
 */
const getPatterns = patterns => {
  patterns = { ...defaultPatterns, ...arrangeKeywords(patterns) }
  const result = [{}, {}, {}]
  for (let key in patterns) {
    const pattern = patterns[key]
    if (/^\{\{.+\}\}$/.test(key)) { result[0][key] = new pattern(); continue }
    result[0][key] = makePattern(pattern)
    result[1][key] = new RegExp(pattern)
    result[2][key] = new RegExp(pattern, 'g')
  }
  return result
}

/**
 * 获取换行符分割符
 * @param {RegExp} rawPattern - 原始模式
 * @returns {string} 换行符
 */
const getNlSpliter = rawPattern => {
  if (!rawPattern) return 'both'
  const format = rawPattern.source.substr(1)
  if (/^win|^(CRLF|\\r\\n)$/i.test(format)) return '\r\n'
  if (/^(unix|LF|\\n)$/i.test(format)) return '\n'
  if (/^mac|^(CR|\\r)$/i.test(format)) return '\r'
  throw new Error(`Invalid NL format: [${format}].`)
}


/**
 * 导出一个函数，用于创建词法分析器类
 * 该函数接收一组模式作为参数，返回一个词法分析器类
 * 词法分析器类用于将源代码字符串分析成词法单元（词元）
 *
 * @param {Object} patterns - 包含各种正则表达式模式的对象，用于词法分析
 * @returns {Class} Lexer - 词法分析器类
 */
module.exports = function makeLexerClass(patterns) {
  // 处理并获取模式对象
  const ptrns = getPatterns(patterns)
  // 返回词法分析器类
  return class Lexer {
    /**
     * 构造函数，初始化词法分析器实例
     *
     * @param {string} src - 源代码字符串
     */
    constructor(src = '') {
      this.name = 'lexer'
      this.src = src
      // 检查源代码是否为字符串，如果不是则抛出错误
      if (typeof src !== 'string') {
        throw new Error(`The type of src, ${typeof src}, is not string.`)
      }
      // 初始化模式对象
      this.patterns = ptrns[0]
      this.aheadPatterns = ptrns[1]
      this.globalPatterns = ptrns[2]
      // 初始化换行符分割器
      const nlSpliter = getNlSpliter(this.patterns['NL'])
      this.lines = new Lines(src, nlSpliter)
    }

    // 以下为属性访问器，用于获取内部状态
    get line() {
      return this.lines.line
    }

    get ln() {
      return this.lines.ln
    }

    get col() {
      return this.line.col
    }

    get eol() {
      return this.line.eol
    }

    get eof() {
      return this.lines.eof
    }

    /**
     * 切换到下一行
     */
    nextLine() {
      this.lines.nextLine()
    }

    /**
     * 获取指定标记的模式
     *
     * @param {string} token - 标记
     * @returns {RegExp} - 标记对应的正则表达式模式
     */
    getPattern(token) {
      if (token in this.patterns) return this.patterns[token]
      this.error(`Undefined token [${token}]`)
    }

    /**
     * 获取预览指定标记的模式
     *
     * @param {string} token - 标记
     * @returns {RegExp} - 标记对应的正则表达式模式
     */
    getAheadPattern(token) {
      if (token in this.aheadPatterns) return this.aheadPatterns[token]
      this.error(`Undefined token [${token}]`)
    }

    /**
     * 获取全局指定标记的模式
     *
     * @param {string} token - 标记
     * @returns {RegExp} - 标记对应的正则表达式模式
     */
    getGlobalPattern(token) {
      if (token in this.globalPatterns) return this.globalPatterns[token]
      this.error(`Undefined token [${token}]`)
    }

    /**
     * 匹配并消耗一个标记
     *
     * @param {string} token - 标记
     */
    eat(token) {
      const matched = this.line.rest.match(this.getPattern(token))
      if (!matched) this.error(`token [${token}]`)
      this.lexeme = matched[0]
      if (this.line.isCutoff) this.line.joinCutoff()
      this.line.advance(this.lexeme.length)
    }

    /**
     * 预览并截断边界
     *
     * @param {string} token - 标记
     * @returns {Lexer} - 词法分析器实例，用于链式调用
     */
    prevent(token) {
      const matched = this.line.rest.match(this.getAheadPattern(token))
      if (matched) this.line.cutoff(matched.index)
      return this
    }

    /**
     * 逃脱预览并截断边界
     *
     * @param {string} token - 标记
     * @param {string} escToken - 逃脱标记
     * @returns {Lexer} - 词法分析器实例，用于链式调用
     */
    escprevent(token, escToken) {
      const matched = this.line.rest.matchAll(this.getGlobalPattern(token))
      if (!matched) return this
      const escMatched = this.line.rest.matchAll(this.getGlobalPattern(escToken))
      const escRanges = []
      let index

      const withinEsc = idx => {
        for (let i = 0; i < escRanges.length; i++) {
          const range = escRanges[i]
          if (idx >= range[0] && idx < range[1]) return true
        }
      }

      for (const match of escMatched) {
        escRanges.push([match.index, match.index + match[0].length])
      }
      for (const match of matched) {
        if (!withinEsc(match.index)) {
          index = match.index;
          break
        }
      }
      if (index >= 0) this.line.cutoff(index)
      return this
    }

    /**
     * 测试当前行剩余部分是否匹配指定标记的模式
     *
     * @param {string} token - 标记
     * @returns {boolean} - 是否匹配
     */
    is(token) {
      const tokens = flatten(Array.from(arguments))
      if (tokens.length > 1) return this.isSeq(tokens)
      if (tokens.length === 1) token = tokens[0]
      if (token === 'NL') return this.eol && !this.eof

      const patternOrTokens = this.getPattern(token)
      const {not, seq, some} = patternOrTokens
      if (not) return !this.is(not)
      if (seq) return this.isSeq(seq)
      if (some) return this.isSome(some)
      if (this.seqData) return this.seqData.eatToTest(token)
      return patternOrTokens.test(this.line.rest)
    }

    /**
     * 测试当前行剩余部分是否匹配一系列指定标记的模式
     *
     * @param {Array<string>} tokens - 标记数组
     * @returns {boolean} - 是否匹配
     */
    isSeq(tokens) {
      if (this.seqData) {
        this.seqData.ref++
      } else {
        this.seqData = new SeqData(this)
      }

      const result = tokens.every(token => this.seqData.eatToTest(token))

      this.seqData.ref--
      if (!this.seqData.ref) delete this.seqData

      return result
    }

    /**
     * 测试当前行剩余部分是否匹配一组指定标记中的任意一个
     *
     * @param {Array<string>} tokens - 标记数组
     * @returns {boolean} - 是否匹配
     */
    isSome(tokens) {
      if (this.seqData) this.seqData.keepState()
      return tokens.some(token => {
        const result = this.is(token)
        if (this.seqData && !result) this.seqData.restoreState()
        return result
      })
    }

    /**
     * 处理一个标记，并执行相应操作
     *
     * @param {string} tkn - 标记
     * @param {Function} act - 操作函数，接收词元作为参数
     */
    token(tkn, act) {
      this.eat(tkn)
      if (act) act(this.lexeme)
    }

    /**
     * 可选地处理一个标记，并执行相应操作
     *
     * @param {string} token - 标记
     * @param {Function} act - 操作函数，接收词元作为参数
     */
    optional(token, act) {
      this.lexeme = ''
      if (this.is(token)) {
        this.eat(token)
      } else {
        if (this.line.isCutoff) this.line.joinCutoff()
      }
      if (act) act(this.lexeme)
    }

    /**
     * 在不包含某个标记的情况下处理操作
     *
     * @param {string} token - 标记
     * @param {Function} act - 操作函数
     */
    without(token, act) {
      this.prevent(token).optional('ALL', lexeme => act && act(lexeme))
    }

    /**
     * 在逃脱地不包含某个标记的情况下处理操作
     *
     * @param {string} token - 标记
     * @param {string} escToken - 逃脱标记
     * @param {Function} act - 操作函数
     */
    escwithout(token, escToken, act) {
      this.escprevent(token, escToken)
          .optional('ALL', lexeme => act(lexeme))
    }

    /**
     * 处理多行不包含某个标记的情况
     *
     * @param {string} token - 标记
     * @param {Function} act - 操作函数
     */
    mlwithout(token, act) {
      const pattern = this.getAheadPattern(token)
      const strs = []
      let matched = this.line.rest.match(pattern)

      while (!matched) {
        strs.push(this.line.rest)
        this.line.advance(this.line.rest.length)
        if (this.eof) break
        this.nextLine()
        matched = this.line.rest.match(pattern)
      }
      if (matched) {
        let without = this.line.rest.substr(0, matched.index)
        strs.push(without)
        this.line.advance(without.length)
      }
      this.lexeme = strs.join('\n')
      if (act) act(this.lexeme)
    }

    /**
     * 抛出错误，包含错误信息和相关源代码行
     *
     * @param {string} message - 错误信息
     */
    error(message) {
      const {ln} = this
      const messageLines = [
        `${message} at line ${this.ln + 1} column ${this.col + 1}.`
      ]
      if (ln > 1) messageLines.push('|' + this.lines.data[ln - 2])
      if (ln > 0) messageLines.push('|' + this.lines.data[ln - 1])
      messageLines.push('|' + this.lines.data[ln])
      messageLines.push(repeat(' ', this.line.col + 1) + '^')
      throw new Error(messageLines.join('\n'))
    }

    /**
     * 可选地跳过单个空格
     */
    skipSS() {
      this.optional('SS')
    }

    /**
     * 跳过所有空白字符和行尾
     */
    skipWhite() {
      while ((this.is('S') || this.eol) && !this.eof) {
        if (this.eol) {
          this.nextLine()
        } else {
          this.token('SS')
        }
      }
    }

    /**
     * 可选地处理一个换行符
     */
    optionalNL() {
      if (this.eol && !this.eof) this.nextLine()
    }

    /**
     * 切换到嵌入的语言词法分析器
     *
     * @param {string} lang - 嵌入语言标识符
     * @returns {Lexer} - 嵌入语言的词法分析器实例
     */
    to(lang) {
      const langLexer = this.patterns[`{{${lang}}}`]
      if (!langLexer) {
        throw new Error(`The embedded lexer {{${lang}}} not defined.`)
      }
      langLexer.lines = this.lines    // both share
    }
  }
}
